﻿//-----------------------------------------------------------------------
// <copyright file="MainForm.cs" company="Open Source">
//     This file is released under the Microsoft Reciprocal License (Ms-RL)
//     http://www.codeplex.com/wiidrumsynth/license
// </copyright>
//-----------------------------------------------------------------------

namespace WiiDrumSynth
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Windows.Forms;
    using Sanford.Multimedia.Midi;
    using WiimoteLib;

    /// <summary>
    /// The Main form for the WiiDrumSynth Application
    /// </summary>
    public partial class MainForm : Form
    {
        /// <summary>
        /// A collection that will hold all connected wiimotes
        /// </summary>
        private WiimoteCollection wiimotes;

        /// <summary>
        /// A MIDI Synth which will be used to output MIDI data
        /// </summary>
        private MidiSynthLib.Synth synth;

        /// <summary>
        /// The last value of acceleration in the z-axis for the WiiMote
        /// </summary>
        private float[][] lastZ;

        /// <summary>
        /// The last value of acceleration in the z-axis for the WiiMote Extension
        /// </summary>
        private float[][] extensionLastZ;

        /// <summary>
        /// The collection of ToolStripStatusLabels - 1 for each of the 4 possibly connected WiiMotes
        /// </summary>
        private ToolStripStatusLabel[] wiiStatus;

        /// <summary>
        /// The collection of ToolStripProgressBars - 1 for each of the 4 possibly connected WiiMotes
        /// </summary>
        private ToolStripProgressBar[] wiiBattery;

        /// <summary>
        /// A hash table mapping a WiiMotes HID Device Path to the WiiMote itself
        /// </summary>
        private Hashtable wiimoteMap;

        /// <summary>
        /// Initializes a new instance of the <see cref="MainForm"/> class.
        /// </summary>
        public MainForm()
        {
            this.InitializeComponent();
            this.synth = new MidiSynthLib.Synth(9, 118);

            this.wiiStatus = new ToolStripStatusLabel[4];
            this.wiiStatus[0] = this.wiiStatus1;
            this.wiiStatus[1] = this.wiiStatus2;
            this.wiiStatus[2] = this.wiiStatus3;
            this.wiiStatus[3] = this.wiiStatus4;

            this.wiiBattery = new ToolStripProgressBar[4];
            this.wiiBattery[0] = this.wiiBattery1;
            this.wiiBattery[1] = this.wiiBattery2;
            this.wiiBattery[2] = this.wiiBattery3;
            this.wiiBattery[3] = this.wiiBattery4;

            this.lastZ = new float[4][];
            this.extensionLastZ = new float[4][];

            for (int i = 0; i < 4; i++)
            {
                this.lastZ[i] = new float[2];
                this.extensionLastZ[i] = new float[2];
            }

            this.wiimoteMap = new Hashtable();

            this.InitializeWiimotes();
        }

        /// <summary>
        /// Sets the text on a ToolStripStatusLabel
        /// </summary>
        /// <param name="label">The ToolStripStatusLabel</param>
        /// <param name="text">The text to set</param>
        private delegate void SetTextDelegate(ToolStripStatusLabel label, string text);

        /// <summary>
        /// Sets the value on a ToolStripProgressBar
        /// </summary>
        /// <param name="bar">The ToolStripProgressBar</param>
        /// <param name="value">The value to set</param>
        private delegate void SetBatteryDelegate(ToolStripProgressBar bar, int value);

        /// <summary>
        /// Initializes the wiimotes.
        /// </summary>
        private void InitializeWiimotes()
        {
            this.wiimoteMap.Clear();
            for (int i = 0; i < 4; i++)
            {
                this.wiiStatus[i].Text = String.Format(CultureInfo.CurrentCulture, "{0}: Off", i);
                this.wiiBattery[i].Value = 0;
            }

            this.wiimotes = new WiimoteCollection();
            this.FindWiimotes();
            for (int i = 0; i < this.wiimotes.Count; i++)
            {
                Wiimote wiimote = this.wiimotes[i];

                this.wiimoteMap.Add(wiimote.HIDDevicePath, i);

                this.wiiStatus[i].Text = String.Format(CultureInfo.CurrentCulture, "{0}: On", i);
                this.wiiBattery[i].Value = (int)wiimote.WiimoteState.Battery;

                wiimote.WiimoteChanged += new EventHandler<WiimoteChangedEventArgs>(this.Wiimote_WiimoteChanged);
                wiimote.WiimoteExtensionChanged += new EventHandler<WiimoteExtensionChangedEventArgs>(this.Wiimote_WiimoteExtensionChanged);

                try
                {
                    wiimote.Connect();
                    if (wiimote.WiimoteState.ExtensionType == ExtensionType.Nunchuk)
                    {
                        wiimote.SetReportType(InputReport.IRExtensionAccel, IRSensitivity.Maximum, true);
                    }
                    else if (wiimote.WiimoteState.ExtensionType == ExtensionType.None)
                    {
                        wiimote.SetReportType(InputReport.IRAccel, IRSensitivity.Maximum, true);
                    }

                    wiimote.SetLEDs(i + 1);
                }
                catch (WiimoteException ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        /// <summary>
        /// Handles the WiimoteExtensionChanged event of the wiimote control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="WiimoteLib.WiimoteExtensionChangedEventArgs"/> instance containing the event data.</param>
        private void Wiimote_WiimoteExtensionChanged(object sender, WiimoteExtensionChangedEventArgs e)
        {
            Wiimote wiimote = sender as Wiimote;
            if (e.Inserted && e.ExtensionType == ExtensionType.Nunchuk)
            {
                wiimote.SetReportType(InputReport.IRExtensionAccel, IRSensitivity.Maximum, true);
            }
            else
            {
                wiimote.SetReportType(InputReport.IRAccel, IRSensitivity.Maximum, true);
            }
        }

        /// <summary>
        /// Sets the text for a <see cref="ToolStripStatusLabel" /> control.
        /// </summary>
        /// <param name="label">The label to set the text on</param>
        /// <param name="text">The text to set</param>
        private void SetText(ToolStripStatusLabel label, string text)
        {
            label.Text = text;
        }

        /// <summary>
        /// Sets the value for a <see cref="ToolStripProgressBar" /> control.
        /// </summary>
        /// <param name="bar">The bar to set the value on</param>
        /// <param name="value">The value to set</param>
        private void SetBattery(ToolStripProgressBar bar, int value)
        {
            bar.Value = value;
        }

        /// <summary>
        /// Handles the WiimoteChanged event of the wiimote control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="WiimoteLib.WiimoteChangedEventArgs"/> instance containing the event data.</param>
        private void Wiimote_WiimoteChanged(object sender, WiimoteChangedEventArgs e)
        {
            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture) + "Z: " + e.WiimoteState.AccelState.Values.Z.ToString(CultureInfo.InvariantCulture));

            // TODO: There is lag introduced somewhere in this method or the methods it calls. Need to investigate and mitigate.
            int id = (int)this.wiimoteMap[((Wiimote)sender).HIDDevicePath];

            string text = String.Format(CultureInfo.CurrentCulture, "{0}: On", id);
            
            if (this.Handle != null)
            {
                Invoke(new SetTextDelegate(this.SetText), new object[] { this.wiiStatus[id], text });
                Invoke(new SetBatteryDelegate(this.SetBattery), new object[] { this.wiiBattery[id], (int)e.WiimoteState.Battery });
            }

            float z = e.WiimoteState.AccelState.Values.Z;
            float extZ = e.WiimoteState.NunchukState.AccelState.Values.Z;

            if (this.lastZ[id][0] < 0 && 
                this.lastZ[id][1] < this.lastZ[id][0] && 
                z > this.lastZ[id][1])
            {
                this.WiiMoteHit(e.WiimoteState.ButtonState, id);
            }

            if (this.extensionLastZ[id][0] < 0 && 
                this.extensionLastZ[id][1] < this.extensionLastZ[id][0] &&
                extZ > this.extensionLastZ[id][1])
            {
                this.NunchuckHit(e.WiimoteState.NunchukState, id);
            }

            this.lastZ[id][0] = this.lastZ[id][1];
            this.lastZ[id][1] = z;

            this.extensionLastZ[id][0] = this.extensionLastZ[id][1];
            this.extensionLastZ[id][1] = extZ;
        }

        /// <summary>
        /// Handles a WiiMote hit.
        /// </summary>
        /// <param name="buttonState">State of the button.</param>
        /// <param name="wiiMoteId">The wii mote id.</param>
        private void WiiMoteHit(WiimoteLib.ButtonState buttonState, int wiiMoteId)
        {
#if DEBUG
            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture) + " - Entering WiiMoteHit");
#endif
            float velocity = (this.lastZ[wiiMoteId][0] * -1) * 127 / 5;
            if (velocity > 127)
            {
                velocity = 127;
            }

            if (velocity < 0)
            {
                velocity = 0;
            }

            int note = 46; // default - open hi-hat
            if (buttonState.A)
            {
                // A = high cymbal
                note = 55;
            }

            if (buttonState.B)
            {
                // B = snare
                note = 38;
            }

            if (buttonState.Left)
            {
                // Left = high tom
                note = 50;
            }

            if (buttonState.Down)
            {
                // down = mid tom
                note = 47;
            }

            if (buttonState.Right)
            {
                // right = low tom
                note = 43;
            }

            // filter out "false" hits
            if (velocity > 40.0)
            {
                this.synth.PlayNote(note, (int)velocity);
            }
#if DEBUG
            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture) + " - Leaving WiiMoteHit");
#endif
        }

        /// <summary>
        /// Handles a Nunchuck hit.
        /// </summary>
        /// <param name="nunchuckState">State of the nunchuck.</param>
        /// <param name="wiiMoteId">The wii mote id.</param>
        private void NunchuckHit(NunchukState nunchuckState, int wiiMoteId)
        {
#if DEBUG
            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture) + " - Entering NunchuckHit");
#endif
            float velocity = (this.extensionLastZ[wiiMoteId][0] * -1) * 127 / 3;
            if (velocity > 127)
            {
                velocity = 127;
            }

            if (velocity < 0)
            {
                velocity = 0;
            }

            int note = 46; // default = open hi-hat
            if (nunchuckState.Z)
            {
                // Z = snare
                note = 38;
            }

            if (nunchuckState.C)
            {
                // C = low cymbal
                note = 49;
            }

            if (nunchuckState.Joystick.X < -0.1)
            {
                // Left = high tom
                note = 50;
            }

            if (nunchuckState.Joystick.Y < -0.1)
            {
                // Down = mid tom
                note = 47;
            }

            if (nunchuckState.Joystick.X > 0.1)
            {
                // Right = low tom
                note = 43;
            }

            // filter out "false" hits
            if (velocity > 40.0)
            {
                this.synth.PlayNote(note, (int)velocity);
            }
#if DEBUG
            Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture) + " - Leaving NunchuckHit");
#endif
        }

        /// <summary>
        /// Finds the wiimotes.
        /// </summary>
        private void FindWiimotes()
        {
            try
            {
                this.wiimotes.FindAllWiimotes();
            }
            catch (WiimoteNotFoundException ex)
            {
                Debug.WriteLine("No Wiimotes found: " + ex.Message);
            }
            catch (WiimoteException ex)
            {
                Debug.WriteLine("Error: " + ex.Message);
            }
        }

        /// <summary>
        /// Handles the Click event of the button1 control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Button1_Click(object sender, EventArgs e)
        {
            this.InitializeWiimotes();
        }

        /// <summary>
        /// Handles the Click event of the mIDISetupToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void MIDISetupToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MidiOptions options = new MidiOptions();
            options.OutDeviceId = this.synth.OutputDeviceId;
            options.ProgramId = this.synth.MidiProgram;
            options.ChannelId = this.synth.MidiChannel;

            if (options.ShowDialog() == DialogResult.OK)
            {
                this.synth.OutputDeviceId = options.OutDeviceId;
                this.synth.MidiChannel = options.ChannelId;
                this.synth.MidiProgram = options.ProgramId;
            }
        }

        /// <summary>
        /// Handles the FormClosing event of the MainForm control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.FormClosingEventArgs"/> instance containing the event data.</param>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            for (int i = 0; i < this.wiimotes.Count; i++)
            {
                Wiimote wiimote = this.wiimotes[i];

                wiimote.WiimoteChanged -= this.Wiimote_WiimoteChanged;
                wiimote.WiimoteExtensionChanged -= this.Wiimote_WiimoteExtensionChanged;
            }

            if (this.synth != null)
            {
                this.synth.Dispose();
                this.synth = null;
            }
        }
    }
}
